# -*- coding: utf-8 -*-

import functools
import logging
from datetime import datetime

from werkzeug.local import LocalProxy
from werkzeug.utils import import_string
from flask import has_request_context, _request_ctx_stack,\
    session, request, current_app, redirect, jsonify
from wechatpy.exceptions import WeChatOAuthException

from mpdev.exceptions import BadConfigurationError
from .models import AnonymousUser, UserMixin

__all__ = ['current_user', 'current_room', 'login_admin', 'logout_admin', 'login_user', 'logout_user',
           'admin_required', 'GzoneLoader']

logger = logging.getLogger(__name__)

current_room = LocalProxy(lambda: _get_room())
current_user = LocalProxy(lambda: _get_user())
# USER_INFO_KEY = ['openid', 'nickname', 'headimgurl']
# 至少应该保留open_id,否则的话需要更改User model的get_instance()方法的签名
USER_INFO_KEY = ['openid']
SESSION_ADMIN_FLAG = 'admin'


def _get_room():
    if has_request_context() and hasattr(_request_ctx_stack.top, 'room'):
        return getattr(_request_ctx_stack.top, 'room')

    room = current_app.gzone_loader.load_room()
    setattr(_request_ctx_stack.top, 'room', room)
    return room


def _get_user():
    if has_request_context() and hasattr(_request_ctx_stack.top, 'user'):
        return getattr(_request_ctx_stack.top, 'user')

    # 如果ctx中不存在user，首先从session中获取user信息
    user = current_app.gzone_loader.load_user() or AnonymousUser()

    setattr(_request_ctx_stack.top, 'user', user)
    return user


# Deprecated
def login_user(user):
    if not user.is_active():
        return False

    for key in USER_INFO_KEY:
        session[key] = getattr(user, key)
    setattr(_request_ctx_stack.top, 'user', user)
    return True


def login_admin(owner_wxid):
    # 用于将密码验证通过后的用户赋予管理员标记
    session[SESSION_ADMIN_FLAG] = owner_wxid
    logger.info(
        'add %s to session[%s].',
        owner_wxid,
    )
    current_user.is_admin = True


# Deprecated
def logout_user():
    for key in USER_INFO_KEY:
        if key in session:
            session.pop(key)
    setattr(_request_ctx_stack.top, 'user', AnonymousUser())


def logout_admin():
    del session[SESSION_ADMIN_FLAG]
    logger.info('delete %s from session', SESSION_ADMIN_FLAG)
    current_user.is_admin = False


def admin_required(f):
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        if current_user.is_admin:
            return f(*args, **kwargs)
        # 这里要求被装饰的页面需要enc_room_id url参数
        # todo: 非admin情况下的错误消息提示
        # 因为是ajax请求,所以这里不返回重定向
        # 而是返回错误信息
        json_data = dict(errors={})
        json_data['errors']['authentication'] = u'需要管理员权限'
        json_data['status_code'] = 403
        logger.warning(
            'Authentication Failed: admin privilege required. Endpoint: %s. Code: %s.',
            request.endpoint,
            json_data['status_code']
        )
        return jsonify(json_data)
        # return redirect(url_for('gzone.index'))
    return wrapper


def binding_required(f):
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        if current_user.is_binding:
            return f(*args, **kwargs)
        # 通过wechatpy.oauth模块生成认证用链接，
        # 并讲redirect_uri设置为当前访问路径
        # request.base_path 不带query args
        current_app.oauth.redirect_uri = request.base_url
        authorize_url = current_app.oauth.authorize_url
        return redirect(authorize_url)
    return wrapper


class GzoneLoader:
    def __init__(self, app=None):
        self.app = app
        self.user_class = None
        self.admin_class = None
        self.room_class = None
        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        try:
            user_class = app.config.get('USER_CLASS')
            user_class = import_string(user_class)
        except ImportError:
            raise BadConfigurationError('用户类配置有误,请检查USER_CLASS')
        if not issubclass(user_class, UserMixin):
            raise BadConfigurationError('User class should inherited from UserMixin')
        self.user_class = user_class

        try:
            admin_class = app.config.get('ADMIN_CLASS')
            admin_class = import_string(admin_class)
        except ImportError:
            raise BadConfigurationError('管理员类配置有误,请检查ADMIN_CLASS')
        self.admin_class = admin_class

        try:
            room_class = app.config.get('ROOM_CLASS')
            room_class = import_string(room_class)
        except ImportError:
            raise BadConfigurationError('群组类配置有误,请检查ROOM_CLASS')
        self.room_class = room_class

        app.config.setdefault('OWNER_LOGIN_AS_ADMIN', True)

        # 1. no template uses these context
        # 2. needs to check None current_room
        # app.context_processor(self._user_context_processor)
        # app.context_processor(self._room_context_processor)

        app.gzone_loader = self
        self.app = app

        logger.debug('User Loader initialized.')

    def load_user(self):
        # Caution: 如果网络错误不能从oauth获取用户信息，
        # 或者非法的code导致无法从auth获取用户信息，
        # 都将导致无限oauth请求！！！ to be fixed
        user_info = self._load_user_info_from_session() or self._load_user_info_from_oauth()
        if user_info is None:
            return AnonymousUser()
        user = self.load_user_from_db(**user_info)
        if user is None:
            if user_info.get('openid') and len(user_info.keys()) == 1:
                return AnonymousUser()
            logger.warning('Unauthenticated user info: %s', user_info)
            return AnonymousUser(oauth=True)

        if user.expired(current_app.config['USER_INFO_EXPIRATION']):
            user_info = self._refresh_user_info(user.refresh_token)
            if user_info is None:
                return AnonymousUser()
            user = self.load_user_from_db(**user_info)
            return user

        if self.app.config['OWNER_LOGIN_AS_ADMIN'] and user.openid == current_room.owner_openid:
            user.is_admin = True
            logger.info('Log user %s as admin automatically because of owner', user.openid)
            return user

        owner_wxid = session.get(SESSION_ADMIN_FLAG)
        if owner_wxid is not None and owner_wxid == current_room.owner_wxid:
            user.is_admin = True
            logger.info(
                'Log user %s as admin accroding session[%s]=%s',
                user.openid,
                SESSION_ADMIN_FLAG,
                owner_wxid
            )
        return user

    def load_user_from_db(self, **kwargs):
        return self.user_class.get_instance(
            current_room._get_current_object(),
            **kwargs
        )

    def _load_user_info_from_session(self):
        user_info = {key: session.get(key) for key in USER_INFO_KEY}
        if all(user_info.values()):
            # 0或者空字符串是不是合法的值？
            return user_info
        logger.info('No session user identity found.')
        return None

    def _load_user_info_from_oauth(self):
        code = request.args.get('code')
        if code is None:
            # 请求参数中没有code，返回None
            # 这种情况下current_user为Anonymous对象
            logger.info('No wechat oauth code received')
            return None
        user_info = None
        try:
            oauth_res = current_app.oauth.fetch_access_token(code)
            user_info = current_app.oauth.get_user_info(
                access_token=oauth_res['access_token'],
                openid=oauth_res['openid']
            )
            user_info['refresh_token'] = oauth_res['refresh_token']
            user_info['bound_time'] = datetime.now()
        except WeChatOAuthException:
            logger.warning(
                'Retrieve user info from OAuth FAILED. Code: %s. user_info: %s',
                code,
                user_info
            )
            return None
        logger.info(
            'Retrieve user info via wechat oauth. Code: {}',
            code
        )
        if not all(user_info[key] for key in USER_INFO_KEY):
            logger.warning(
                'No validate user info received from wechat oauth. user_info: %s',
                user_info.items().__str__()
            )
            return None

        logger.info(
            'Retrieve user info from OAuth SUCCESSFULLY. '
            'Openid: %s. Nickname: %s. Headimgurl: %s',
            user_info['openid'],
            user_info['nickname'],
            user_info['headimgurl']
        )

        # 保存到session
        for key in USER_INFO_KEY:
            session[key] = user_info[key]

        return user_info

    def _refresh_user_info(self, refresh_token):
        user_info = None
        try:
            oauth_res = current_app.oauth.refresh_access_token(refresh_token)
            user_info = current_app.oauth.get_user_info(
                access_token=oauth_res['access_token'],
                openid=oauth_res['openid']
            )
        except WeChatOAuthException:
            logger.warning(
                'Refresh user info from OAuth FAILED. refresh_token: %s. user_info: %s',
                refresh_token,
                user_info
            )
            for key in USER_INFO_KEY:
                session.pop(key, None)
            return None
        if not all(user_info[key] for key in USER_INFO_KEY):
            logger.warning(
                'No validate user info received from wechat oauth. user_info: %s',
                user_info.items().__str__()
            )
            return None
        user_info['refresh_token'] = oauth_res['refresh_token']
        user_info['bound_time'] = datetime.now()
        logger.info(
            'Refresh user info from OAuth SUCCESSFULLY. '
            'Openid: %s. Nickname: %s. Headimgurl: %s. Refresh_token: %s.',
            user_info['openid'],
            user_info['nickname'],
            user_info['headimgurl'],
            user_info['refresh_token']
        )
        return user_info

    def load_room(self):
        logger.debug('Load room info BEGIN. Endpoint: %s', request.endpoint)
        room_id = session.get('room_id')
        if room_id is None:
            return None
        owner_wxid = current_app.redis_interface.get_room_owner(room_id)
        # 注意要加上对room.status的处理
        room = self.room_class.query.get(room_id)
        if owner_wxid is None:
            logger.warning('Someone is requesting for invalid/disabled room: %s.', room_id)
            if room is not None and room.enabled:
                room.disable()
                logger.info('Disable room %s in MySQL as room is no longer valid', room_id)
            session.pop('enc_room_id', None)
            logger.info('CLEAR session: enc_roomid')
            session.pop('room_id', None)
            logger.info('CLEAR session: roomid')
            return None
        if room is None:
            room = self.room_class.get_instance(room_id)
            logger.info('Room %s created in MySQL', room_id)
        elif room.disabled:
            room.enable()
            logger.info('Enabling room %s in MySQL as room is valid again', room_id)
        # self._refresh_room_user(room_id)
        # logger.info('Refreshing users to room %s in MySQL', room_id)
        room.owner_wxid = owner_wxid

        if self.app.config['OWNER_LOGIN_AS_ADMIN']:
            # here dependency to flask_sqlalchemy!
            room.owner_openid = current_app.extensions['sqlalchemy'].db.session\
                .query(self.admin_class.openid).filter_by(wxid=owner_wxid).scalar()
            logger.debug('Querying mysql for owner openid: %s', room.owner_openid)
        room.enc_room_id = session.get('enc_room_id')
        return room

    def _load_room_from_redis(self, room_id):
        pass

    def _refresh_room_user(self, room_id):
        user_r = current_app.redis_interface.get_room_users(room_id)

    def _user_context_processor(self):
        return {'current_user': _get_user()}

    def _room_context_processor(self):
        return {'current_room': _get_room()}
